
/*
 ---------------------------------------------------------------------
Capture.cs

While the underlying libraries are covered by LGPL, this sample is released 
as public domain.  It is distributed in the hope that it will be useful, but 
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
or FITNESS FOR A PARTICULAR PURPOSE.  

---------------------------------------------------------------------

Simple DirectShow frame grabber.
 
 This is file is pretty much word for word copy of DirectShowLib WebCam sample
*/

using System;
using System.Collections.Generic;
using System.Text;
using DirectShowLib;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;

namespace ScannerLib.FrameGrabber
{
    public class Capture : ISampleGrabberCB, IDisposable
    {

        /// <summary> graph builder interface. </summary>
        private IFilterGraph2 m_filterGraph = null;
        private IMediaControl m_mediaCtrl = null;

        /// <summary> so we can wait for the async job to finish </summary>
        private ManualResetEvent m_pictureReady = null;

        /// <summary> Set by async routine when it captures an image </summary>
        private volatile bool m_gotOne = false;

        /// <summary> Indicates the status of the graph </summary>
        private volatile bool m_running = false;

        /// <summary> Dimensions of the image, calculated once in constructor. </summary>
        private IntPtr m_handle = IntPtr.Zero;
        private int m_videoWidth;
        private int m_videoHeight;
        private int m_stride;
        private  int m_dropped = 0;

        
        public Capture()
        {
            Init(0, 0, 0, 0);
        }
        /// <summary> Use specified capture device, default frame rate and size</summary>
        public Capture(int iDeviceNum)
        {
            Init(iDeviceNum, 0, 0, 0);
        }
        /// <summary> Use specified capture device, specified frame rate and default size</summary>
        public Capture(int iDeviceNum, int iFrameRate)
        {
            Init(iDeviceNum, iFrameRate, 0, 0);
        }
        /// <summary> Use specified capture device, specified frame rate and size</summary>
        public Capture(int iDeviceNum, int iFrameRate, int iWidth, int iHeight)
        {
            Init(iDeviceNum, iFrameRate, iWidth, iHeight);
        }

        ~Capture()
        {
            Dispose();
        }

        public int Width
        {
            get
            {
                return m_videoWidth;
            }
        }

        public int Height
        {
            get
            {
                return m_videoHeight;
            }
        }

        public int Stride
        {
            get
            {
                return m_stride;
            }
        }

        public bool Started
        {
            get
            {
                return m_running;
            }
        }

        public IntPtr GetBitMap()
        {

            if (!Started)
                throw new Exception("Must start first, then acquire bitmaps");

            IntPtr finalHandle = IntPtr.Zero;
            try
            {
                finalHandle = Marshal.AllocCoTaskMem(m_stride * m_videoHeight);
                CopyMemory(finalHandle, m_handle, m_stride * m_videoHeight);
                Marshal.FreeCoTaskMem(m_handle);
                m_gotOne = false;
            }
            catch
            {
                
                throw;
            }

            // Got one
            return finalHandle;
        }

        public void Start()
        {
            if (!m_running)
            {
                m_gotOne = false;
                int hr = m_mediaCtrl.Run();
                DsError.ThrowExceptionForHR(hr);

                m_running = true;
            }
        }

        public void Pause()
        {
            if (m_running)
            {
                int hr = m_mediaCtrl.Pause();
                DsError.ThrowExceptionForHR(hr);

                m_running = false;
            }
        }

        private void Init(int deviceNum, int frameRate, int width, int height)
        {
            DsDevice[] capDevices;

            // Get the collection of video devices
            capDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);

            if (deviceNum + 1 > capDevices.Length)
            {
                throw new Exception("No video capture devices found at that index!");
            }

            try
            {
                // Set up the capture graph
                SetupGraph(capDevices[deviceNum], frameRate, width, height);

                // tell the callback to ignore new images
                m_pictureReady = new ManualResetEvent(false);
                m_gotOne = false;
                m_running = false;
            }
            catch
            {
                Dispose();
                throw;
            }
        }

        private void SetupGraph(DsDevice dev, int frameRate, int width, int height)
        {
            int hr;

            ISampleGrabber sampGrabber = null;
            IBaseFilter capFilter = null;
            ICaptureGraphBuilder2 capGraph = null;

            // Get the graphbuilder object
            m_filterGraph = (IFilterGraph2)new FilterGraph();
            m_mediaCtrl = m_filterGraph as IMediaControl;
            try
            {
                // Get the ICaptureGraphBuilder2
                capGraph = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();

                // Get the SampleGrabber interface
                sampGrabber = (ISampleGrabber)new SampleGrabber();

                // Start building the graph
                hr = capGraph.SetFiltergraph(m_filterGraph);
                DsError.ThrowExceptionForHR(hr);

                // Add the video device
                hr = m_filterGraph.AddSourceFilterForMoniker(dev.Mon, null, "Video input", out capFilter);
                DsError.ThrowExceptionForHR(hr);

                IBaseFilter baseGrabFlt = (IBaseFilter)sampGrabber;
                ConfigureSampleGrabber(sampGrabber);

                // Add the frame grabber to the graph
                hr = m_filterGraph.AddFilter(baseGrabFlt, "Ds.NET Grabber");
                DsError.ThrowExceptionForHR(hr);

                // If any of the default config items are set
                if (frameRate + height + width > 0)
                {
                    SetConfigParms(capGraph, capFilter, frameRate, width, height);
                }

                hr = capGraph.RenderStream(PinCategory.Capture, MediaType.Video, capFilter, null, baseGrabFlt);
                DsError.ThrowExceptionForHR(hr);

                SaveSizeInfo(sampGrabber);
            }
            finally
            {
                if (capFilter != null)
                {
                    Marshal.ReleaseComObject(capFilter);
                    capFilter = null;
                }
                if (sampGrabber != null)
                {
                    Marshal.ReleaseComObject(sampGrabber);
                    sampGrabber = null;
                }
                if (capGraph != null)
                {
                    Marshal.ReleaseComObject(capGraph);
                    capGraph = null;
                }
            }
        }

        private void SaveSizeInfo(ISampleGrabber sampGrabber)
        {
            int hr;

            // Get the media type from the SampleGrabber
            AMMediaType media = new AMMediaType();
            hr = sampGrabber.GetConnectedMediaType(media);
            DsError.ThrowExceptionForHR(hr);

            if ((media.formatType != FormatType.VideoInfo) || (media.formatPtr == IntPtr.Zero))
            {
                throw new NotSupportedException("Unknown Grabber Media Format");
            }

            // Grab the size info
            VideoInfoHeader videoInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(media.formatPtr, typeof(VideoInfoHeader));
            m_videoWidth = videoInfoHeader.BmiHeader.Width;
            m_videoHeight = videoInfoHeader.BmiHeader.Height;
            m_stride = m_videoWidth * (videoInfoHeader.BmiHeader.BitCount / 8);

            DsUtils.FreeAMMediaType(media);
            media = null;
        }
        
        private void ConfigureSampleGrabber(ISampleGrabber sampGrabber)
        {
            AMMediaType media;
            int hr;

            // Set the media type to Video/RBG24
            media = new AMMediaType();
            media.majorType = MediaType.Video;
            media.subType = MediaSubType.RGB24;
            media.formatType = FormatType.VideoInfo;
            hr = sampGrabber.SetMediaType(media);
            DsError.ThrowExceptionForHR(hr);

            DsUtils.FreeAMMediaType(media);
            media = null;

            // Configure the samplegrabber
            hr = sampGrabber.SetCallback(this, 1);
            DsError.ThrowExceptionForHR(hr);
        }

        #region IDisposable Members

        public void Dispose()
        {
            GC.SuppressFinalize(this);
            CloseInterfaces();
            if (m_pictureReady != null)
            {
                m_pictureReady.Close();
                m_pictureReady = null;
            }
        }

        #endregion

        private void SetConfigParms(ICaptureGraphBuilder2 capGraph, IBaseFilter capFilter, int frameRate, int width, int height)
        {
            int hr;
            object o;
            AMMediaType media;

            // Find the stream config interface
            hr = capGraph.FindInterface(
                PinCategory.Capture, MediaType.Video, capFilter, typeof(IAMStreamConfig).GUID, out o);

            IAMStreamConfig videoStreamConfig = o as IAMStreamConfig;
            if (videoStreamConfig == null)
            {
                throw new Exception("Failed to get IAMStreamConfig");
            }

            // Get the existing format block
            hr = videoStreamConfig.GetFormat(out media);
            DsError.ThrowExceptionForHR(hr);

            // copy out the videoinfoheader
            VideoInfoHeader v = new VideoInfoHeader();
            Marshal.PtrToStructure(media.formatPtr, v);

            // if overriding the framerate, set the frame rate
            if (frameRate > 0)
            {
                v.AvgTimePerFrame = 10000000 / frameRate;
            }

            // if overriding the width, set the width
            if (width > 0)
            {
                v.BmiHeader.Width = width;
            }

            // if overriding the Height, set the Height
            if (height > 0)
            {
                v.BmiHeader.Height = height;
            }

            // Copy the media structure back
            Marshal.StructureToPtr(v, media.formatPtr, false);

            // Set the new format
            hr = videoStreamConfig.SetFormat(media);
            DsError.ThrowExceptionForHR(hr);

            DsUtils.FreeAMMediaType(media);
            media = null;
        }

        private void CloseInterfaces()
        {
            int hr;

            try
            {
                if (m_mediaCtrl != null)
                {
                    // Stop the graph
                    hr = m_mediaCtrl.Stop();
                    m_running = false;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }

            if (m_filterGraph != null)
            {
                Marshal.ReleaseComObject(m_filterGraph);
                m_filterGraph = null;
            }
        }

        public event EventHandler FrameReady;

        protected void OnFrameReady(EventArgs s)
        {
            if (FrameReady != null)
            {
                FrameReady(this, s);
            }
        }

        #region ISampleGrabberCB Members

        int ISampleGrabberCB.SampleCB(double SampleTime, IMediaSample pSample)
        {
            if (!Started)
                return 0;

            if (!m_gotOne)
            {
                m_gotOne = true;
                // Set bGotOne to prevent further calls until we
                // request a new bitmap.
                m_gotOne = true;
                IntPtr pBuffer;

                pSample.GetPointer(out pBuffer);
                int iBufferLen = pSample.GetSize();

                if (pSample.GetSize() > m_stride * m_videoHeight)
                {
                    throw new Exception("Buffer is wrong size");
                }

                CopyMemory(m_handle, pBuffer, m_stride * m_videoHeight);

                // Picture is ready.
                m_pictureReady.Set();
                OnFrameReady(EventArgs.Empty);
            }

            Marshal.ReleaseComObject(pSample);
            return 0;
        }

        /// <summary> buffer callback, COULD BE FROM FOREIGN THREAD. </summary>
        int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)
        {
            if (!Started)
                return 0;

            if (!m_gotOne)
            {
                m_gotOne = true;
                // The buffer should be long enought
                if (BufferLen <= m_stride * m_videoHeight)
                {
                    // Copy the frame to the buffer
                    m_handle = Marshal.AllocCoTaskMem(m_stride * m_videoHeight);
                    CopyMemory(m_handle, pBuffer, m_stride * m_videoHeight);
                   
                }
                else
                {
                    throw new Exception("Buffer is wrong size");
                }

                // Set bGotOne to prevent further calls until we
                // request a new bitmap.
                

                // Picture is ready.
                m_pictureReady.Set();
                OnFrameReady(EventArgs.Empty);
            }
            else
            {
                m_dropped++;
            }
            return 0;
        }

        #endregion

        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
        public static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);


    }
}
